
Este arquivo é de um padrão conhecido como "notebook', produzido em um ambiente chamado Jupyter Notebook. Entre as várias facilidades que apresenta, ele permite que pessoas que não têm domínio sobre programação possam ler um projeto de Ciência de Dados e compreender o caminho feito pelo cientista do início até suas conclusões.
Isso é feito com uma combinação de códigos em Python (e outras linguagens paralelas, como HTML) alternados por textos comuns para que o leitor vá acompanhando o passo a passo. Neste projeto há títulos para sessões e comentários do que está sendo feito a cada etapa, cada um com sua própria formatação. Exemplos dessa aplicação você vê abaixo.
Este é um comentário do tipo 1, que poderá ser inserido antes e depois de blocos de código para informar o leitor do que está sendo feito por meio deles.
Este é um comentário do tipo 2, que será geralmente inserido após títulos e gráficos, ou para comentários secundários. Neste caso usaremos este comentário para explicar que a linha abaixo é código Python.
print("Estas duas linhas são código em Python. A de cima é o comando feito, e a de baixo é o que retorna desse comando.")
Este é um box de conclusões. Ele será utilizado ao fim de cada sessão para descrever os resultados encontrados para aquele requisito.
Este comentário sem formatação é do tipo 3, que será usado em situações específicas, por exemplo, para exibição de listas, como as que apresentaremos logo abaixo, na Introdução ao projeto.
Vamos iniciar o trabalho importando abaixo as bibliotecas necessárias para cumprir os requisitos apresentados.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from wordcloud import WordCloud, STOPWORDS
from datetime import datetime, timedelta
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn import metrics
from scipy.stats import pearsonr
import seaborn as sns
import random
import re
from sklearn import preprocessing
import operator
plt.rcParams.update({'font.size': 15})
plt.rcParams['figure.figsize'] = 18, 5
pd.set_option('display.max_colwidth', 50)
A seguir carregamos os arquivos csv em variáveis de formato DataFrame - o padrão para trabalhar dados com a biblioteca Pandas. A cada arquivo carregado, nós exibiremos as 5 primeiras linhas desses datasets para exploração inicial com o método "head", e também para verificar se não houve nenhum erro de codificação ou de outro tipo.
customers = pd.read_csv('datasets/olist_customers_dataset.csv')
customers.head()
geoloc = pd.read_csv('datasets/olist_geolocation_dataset.csv')
geoloc.head()
order_items = pd.read_csv('datasets/olist_order_items_dataset.csv')
order_items.head()
order_payments = pd.read_csv('datasets/olist_order_payments_dataset.csv')
order_payments.head()
order_reviews = pd.read_csv('datasets/olist_order_reviews_dataset.csv')
Parece que esse dataset tem colunas com dados inconsistentes, ou preenchidas com dados que não parecem fazer sentido no todo. Vamos verificar a composição desse dataset para conhecê-lo melhor.
order_reviews.head()
order_reviews[['Unnamed: 7', 'Unnamed: 8', 'Unnamed: 9', 'Unnamed: 10', 'Unnamed: 11', 'Unnamed: 12']].describe()
Chama atenção aqui a baixa quantidade de valores únicos (que não se repetem) nessas colunas que vão de Unnamed7 a Unnamed12 (o valor *unique*, descrito na segunda linha da tabela acima). Quando há poucos valores únicos, há muitos valores repetidos, e pelas 5 primeiras linhas desse dataset vemos que essas colunas provavelmente estão populadas com valores nulos.
Vamos verificar as oitenta primeiras linhas da coluna "Unnamed 7" (a que contém mais valores únicos), e ver quantos valores únicos esse recorte tem.
order_reviews['Unnamed: 7'].iloc[0:80].unique()
A coluna analisada tem valores nulos nas suas 80 primeiras linhas. Assim, para termos um dataset mais limpo, justifica-se simplesmente remover essa e outras colunas similares (as Unnmaed de 7 a 12). A remoção nós faremos abaixo.
order_reviews = order_reviews.drop(order_reviews.columns[7:13], axis=1)
order_reviews.head()
E continuamos a carregar os datasets.
orders = pd.read_csv('datasets/olist_orders_dataset.csv')
orders.head()
products = pd.read_csv('datasets/olist_products_dataset.csv')
products.head()
sellers = pd.read_csv('datasets/olist_sellers_dataset.csv')
sellers.head()
product_category_name_translation = pd.read_csv('datasets/product_category_name_translation.csv')
product_category_name_translation
Este dataset "product_category_name_translation" não contém informações relevantes à nossa análise, então removeremos a sua variável para usarmos menos memória.
del product_category_name_translation
Carregados e conhecidos os datasets, vamos começar o trabalho em si.
Ao invés de apresentarmos números para cumprir esse requisito, vamos usar uma nuvem de palavras, que será capaz de nos ajudar a compreender de maneira bem mais visual os termos mais comuns utilizados pelos usuários do Olist em seus reviews.
Vamos analisar as 10 primeiras linha do dataset que contém os reviews para conhecermos um recorte da sua composição.
order_reviews.head(10)
Já de início é possível perceber que há uma quantidade de valores nulos nessa coluna, o que poderá atrapalhar a criação de uma nuvem de palavras. Vamos verificar a aparição de valores nulos.
order_reviews.isnull().sum()
Das 10.000 entradas que há neste dataset, 88.285 são nulas na coluna *review_comment_title* e 58.246 são nulas na coluna *review_comment_message*. Para termos uma nuvem que forneça algum insight, vai ser preciso criar um novo dataset a partir desse, mas sem essas linhas que contém valores nulos.
order_reviews_not_null = order_reviews.dropna()
Criado o novo dataset, vamos verificar quantos valores nulos existem na coluna review_comment_title e na review_comment_message
order_reviews_not_null['review_comment_title'].isnull().sum()
order_reviews_not_null['review_comment_message'].isnull().sum()
Ok, agora podemos seguir com a nuvem de palavras. Vamos tentar construir uma nuvem com os reviews e verificar se é possível ter um bom retorno do que os clientes estão dizendo.
mensagens = order_reviews_not_null['review_comment_message'].str.cat(sep=' ')
mensagens = mensagens.replace('que', '').replace('de', '').replace('porém', '').replace('para', ' ').replace('por', '').replace('uma', '').replace('pois', '').replace('um', '').replace('ma', '')
wordcloud = WordCloud(background_color='white', scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
A nuvem feita com os reviews apresentou os termos "prazo" e "muito bom" como bastante relevantes no universo dos clientes que deixaram comentários. A aparição frequente de termos relacionados como "Entrega", "chegou antes", "bem antes", "entrega antes", "entrega rápida" e "recomendo" em tamanhos relevantes na nuvem nos leva a crer que ao menos numa análise preliminar há um certo grau de satisfação dos clientes com as entregas feitas a partir do Olist.
Para entender o que as pessoas estão falando sobre prazo, vamos dar uma rápida olhada nos reviews que contêm essa palavra.
order_reviews_not_null[order_reviews_not_null['review_comment_message'].str.contains('prazo')]['review_comment_message']
Pela amostra, parece realmente que os clientes que falam sobre prazo estão satisfeitos com suas compras. Vamos verificar a pontuação que esses clientes em específico deram às suas transações.
prazo_pontos = order_reviews_not_null[order_reviews_not_null['review_comment_message'].str.contains('prazo')]['review_score'].value_counts()
graph_prazo_pontos = prazo_pontos.plot(kind='bar', rot=0)
graph_prazo_pontos.set_xlabel('Nota')
graph_prazo_pontos.set_ylabel('Pessoas que usaram o termo "prazo"')
Pela amostra, não há dúvida. No período analisado, os clientes que comentaram sobre prazos ficaram bastante satisfeitos com as entregas feitas a partir do Olist, observando que por volta de 1.400 deles deram nota 5/5 para sua transação.
Vamos agora verificar os títulos dos reviews
titulos = order_reviews_not_null['review_comment_title'].str.cat(sep=' ')
wordcloud = WordCloud(background_color="white", scale=2).generate(titulos)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
Com uma grande quantidade de termos positivos, a nuvem produzida com os títulos dos reviews confirma a satisfação dos clientes observada nas mensagens dos reviews.
Os comentários deixados pelos clientes do Olist, tanto nos reviews quanto nos títulos, indicam que eles estão satisfeitos. Colateralmente, descobrimos também que a velocidade na entrega é um dos maiores fatores contribuindo para essa satisfação.
Já que chegamos a uma conclusão nesse requisito, vamos apagar essas variáveis para que não continuem a ocupar espaço na memória.
del titulos
del wordcloud
del graph_prazo_pontos
del prazo_pontos
del order_reviews_not_null
del mensagens
Para responder a essa pergunta, vamos voltar ao dataset de reviews original e analisar como os clientes que não deixaram comentário sobre sua compra avaliaram sua transação.
order_reviews[order_reviews['review_comment_message'].isnull()]
sem_coment = order_reviews[order_reviews['review_comment_message'].isnull()]
graph_sem_coment = sem_coment['review_score'].value_counts().plot(kind='bar', rot=0)
graph_sem_coment.set_xlabel('Nota')
graph_sem_coment.set_ylabel('Número de clientes')
del graph_sem_coment
Com mais de 35.000 clientes dando nota 5 para suas transações, aparentemente, esses estão mesmo satisfeitos. Vamos saber mais sobre esses clientes que não deixaram review, começando pela análise dos produtos que compraram.
A partir da tabela em que estamos (de reviews) não é possível obter diretamente informações sobre itens comprados, então vai ser necessário fundir esta tabela com outras, seguindo o esquema do banco de dados abaixo disponibilizado pela equipe Olist. Faremos fusão com os dois datasets relacionados a produtos, que são "olist_order_items_dataset" e "olist_products_dataset".
Mas para chegar lá, se estamos partindo da tabela "olist_order_reviews_dataset" precisaremos passar antes pela tabela "olist_orders_dataset".

Vamos continuar as fusões.
orders['order_id'] = pd.Series(orders['order_id'], dtype='string')
orders['order_id'].head()
sem_coment['order_id'] = pd.Series(sem_coment['order_id'], dtype='string')
sem_coment['order_id'].head()
E agora vamos fundir esses dois datasets.
sem_coment_full = sem_coment.merge(orders, how='inner', on='order_id' )
Datasets fundidos, vamos partir para a fusão com os outros especificamente relacionados a itens: "olist_order_items_dataset" e "olist_products_dataset".
sem_coment_full = sem_coment_full.merge(order_items, how='inner', on='order_id')
sem_coment_full = sem_coment_full.merge(products, how='inner', on='product_id')
Agora vamos verificar o estado do nosso dataset completo, com dados de reviews, dados de pedidos e dados de produtos:
sem_coment_full.info()
Temos 17 colunas e 64.729 entradas. Sem erros nas fusões, está tudo aí pelo visto. Checaremos rapidamente as categorias de produtos mais compradas.
sem_coment_full['product_category_name'].value_counts().head(10).plot(kind='bar', rot=45)
plt.ylabel('Quantidade de produtos comprados')
E agora vamos conhecer alguns sumários estatísticos sobre como esses clientes gastaram nas 4 primeiras categorias. Os que nos interessam são média gasta por esses clientes em cada categoria (mostrada nas tabelas abaixo no item "mean) e o quartil 50%, que expressa o gasto máximo de metade dos compradores nessas categorias.
Cama, mesa e banho
sem_coment_full[sem_coment_full['product_category_name'] == 'cama_mesa_banho']['price'].describe()
Beleza e Saúde
sem_coment_full[sem_coment_full['product_category_name'] == 'beleza_saude']['price'].describe()
Esporte e lazer
sem_coment_full[sem_coment_full['product_category_name'] == 'esporte_lazer']['price'].describe()
Móveis e decoração
sem_coment_full[sem_coment_full['product_category_name'] == 'moveis_decoracao']['price'].describe()
Nada realmente saltou aos olhos aqui. Vamos verificar os gastos de clientes no geral para sabermos se eles se diferenciam deste segmento que não deixou mensagem.
gastos_geral = order_items.merge(products, on='product_id', how='inner')
gastos_geral['price'].describe()
gastos_geral[gastos_geral['product_category_name'] == 'cama_mesa_banho']['price'].describe()
gastos_geral['product_category_name'].value_counts()
gastos_geral = gastos_geral.merge(order_reviews, on='order_id', how='inner')
As características das compras dos clientes que não deixaram review se assemelham muito ao conjunto total dos clientes. Vamos checar agora apenas os que deixaram review para vermos se há alguma diferença. Novas fusões vão ser necessárias.
com_coment_full = gastos_geral[gastos_geral['review_comment_message'].notnull()]
com_coment_full = com_coment_full.merge(orders, on='order_id', how='inner')
com_coment_full = com_coment_full.merge(customers, on='customer_id', how='inner')
Feitas as fusões necessárias, agora é verficar as categorias mais compradas pelo conjunto dos clientes que deixaram reviews.
com_coment_full['product_category_name'].value_counts()
Realmente, as categorias se assemelham bastante aos outros dois conjuntos, o dos clientes que não deixaram review, e o dos clientes em geral. Vamos verificar as notas que deram às suas transações os clientes que deixaram review.
com_coment_full_graph = com_coment_full['review_score'].value_counts().plot(kind='bar', rot=0)
com_coment_full_graph.set_xlabel('Nota')
com_coment_full_graph.set_ylabel('Número de clientes')
Além das categorias, as notas também seguem o mesmo padrão dos outros conjuntos. Vamos verificar os gastos.
com_coment_full['price'].describe()
Os clientes que não deixaram review reproduzem praticamente os mesmos comportamentos de consumo do público geral nos produtos comprados e nos valores gastos, motivo provável pelo qual também reproduzem os mesmos níveis de satisfação.
Conforme fizemos antes, agora vamos remover as variáveis usadas para que não ocupem espaço na memória.
del sem_coment_full
del sem_coment
del gastos_geral
del com_coment_full_graph
Para cumprir este requisito nós iremos apontar a tendência geral das vendas para o futuro utilizando a técnica de regressão linear. Por meio desse modelo criaremos uma linha de tendência que será projetada dois anos à frente do último dia útil do dataset.
Vamos fundir dois datasets que contém informações importantes sobre vendas, preços e datas. São eles: "olist_orders_dataset" e "olist_order_items_dataset"
compras = orders.merge(order_items, on='order_id', how='inner')
compras.head()
O novo dataset chamado "compras" foi criado com a fusão dos dois e está funcionando corretamente. Vamos agora conhecer a tendência geral das vendas por mês. Veremos por alto a média e soma total das vendas por esse recorte. Mas antes, vamos limpar a colunda de data de pedido aprovado, para deixarmos nela só meses e anos.
compras['order_approved_at'] = compras['order_approved_at'].str.replace('-\d\d \d\d:\d\d:\d\d', '')
compras['order_approved_at'] = pd.to_datetime(compras['order_approved_at'])
A coluna está num formato em que é possível aplicar operações no Python. Agora iremos plotar a soma de vendas por mês de cada ano.
pd.pivot_table(compras,index='order_approved_at', values='price', aggfunc='sum').plot()
É bastante estranho que não haja nenhuma venda em dois períodos: entre dezembro de 2016 e janeiro de 2017, e de agosto de 2018 para frente. Vamos ver como se comportam as vendas especificamente nos três meses que antecedem a queda no final da série.
periodo_de_queda = compras[(compras['order_approved_at'] < '2018-10') & (compras['order_approved_at'] > '2018-07')]
pd.pivot_table(periodo_de_queda,index='order_approved_at', values='price', aggfunc='sum').plot()
Segundo os dados, a soma das vendas foi caindo solidamente, de mais de 800.000 em agosto, até chegar a zero no dia 1º de setembro, se mantendo de forma problemática até o final de dezembro de 2018. Sendo este quadro pouco provável segundo a realidade, a hipótese é de que exista um problema com este dataset, ou com o banco de dados, no início e no fim da série temporal.
Sabendo que essas duas partes dos dados (que marcam o início e o fim da série temporal) não são confiáveis, vamos simplesmente remover esses meses da nossa análise, para evitar criarmos uma estatística distorcida.
compras_clean = compras[(compras['order_approved_at'] >= '2017-01') & (compras['order_approved_at'] < '2018-08-01')]
E agora vamos plotar a soma das vendas por mês sem esses períodos.
pd.pivot_table(compras_clean,index='order_approved_at', values='price', aggfunc='sum').plot()
Agora o gráfico da soma das vendas parece muitos mais realista. Já é possível perceber uma tendência.
Na ausência de outras variáveis que impactem sobre as vendas (alteração de preços, verba para anúncios, impressões de anúncios, engajamento etc.), utilizaremos uma linha de tendência simples para projetar as vendas. Vamos aplicar sobre a coluna 'Price' agrupada pela data o recurso de Regressão Linear para saber como seria o restante do ano, entre agosto e setembro de 2018, tomando como hipótese que a tendência se manterá essa que está registrada. O processo se inicia formatando o DataFrame adequadamente.
compras_clean = orders.merge(order_items, on='order_id', how='inner')
compras_clean.head()
compras_clean['order_approved_at'] = compras_clean['order_approved_at'].str.replace(' \d\d:\d\d:\d\d', '')
compras_clean['order_approved_at']
Decidimos utilizar para análise e treinamento do nosso modelo o período de 12 meses entre julho de 2017 e julho de 2018. Esse período é interessante porque ele é capaz de captar a sazonalidade das vendas.
compras_clean = compras_clean[(compras_clean['order_approved_at'] >= '2017-06-01') & (compras_clean['order_approved_at'] < '2018-08-01')]
compras_clean.info()
compras_clean['order_approved_at'].head()
Contendo 92.041 linhas e as colunas que nos interessam (a data de pagamento, em order_approved_at, e o preço do produto comprado) o DataFrame parece adequado.
O algoritmo de regressão linear não aceita como entrada o formato de data, então teremos que usar o número de dias passados a partir de 01/06/2017 para acompanhar a evolução das vendas. Agora nós iremos inserir uma coluna com esse valor, e depois poderemos fazer previsões utilizando qualquer número de dias depois de 01/06/2017.
compras_clean = compras_clean.groupby(by='order_approved_at').agg('sum')
compras_clean['data_compra'] = compras_clean.index
compras_clean.reset_index(drop=True, inplace=True)
compras_clean['dias'] = compras_clean.index
compras_clean.tail()
compras_clean.describe()
Agora nós temos a coluna "dias" para uso nas nossas previsões.
Vamos agora criar um gráfico do tipo dispersão, para verificar a relação dos dias passados a partir de 01/06/2017, e o valor total em vendas diárias, para acompanharmos sua progressão.
compras_clean.plot(kind='scatter', x='dias', y='price')
Vamos remover os quatro valores acima de 80000, sabendo que eles não representam bem o dataset como um todo.
compras_clean = compras_clean[compras_clean['price'] < 60000]
Numa rápida olhada é possível perceber uma correlação leve e positiva entre as variáveis dias passados e vendas totais por dia. Isso quer dizer que elas tendem a aumentar juntas.
Como dissemos antes, não temos outras variáveis de real impacto nas vendas, então vamos simplesmente assumir a tendência conforme o tempo passa. Agora, verificamos a força da correlação entre Dias e Vendas em R$ por dia com o recurso da Correlação de Pearson. Se esse resultado estiver acima de 4.0, esssas variáveis têm correlação suficiente para aplicar o modelo.
corr, _ = pearsonr(compras_clean['dias'], compras_clean['price'])
corr
Tudo certo, a correlação é positiva e moderada, e agora, iremos utilizar o modelo estatístico escolhido em si, Regressão Linear Simples.
X = compras_clean['dias'].values.reshape(-1, 1)
y = compras_clean['price'].values.reshape(-1, 1)
Como uma boa prática do Machine Learning, nós separamos o nosso dataset em duas porções, uma para treino e uma para teste do modelo. A de treino será usada para a máquina entender os padrões dos dados, e a de teste será usada para comparação futura: a inteligência irá tentar recriar os dados que ficaram reservados nessa porção, e nós poderemos comparar os resultados depois para averiguar sua eficiência.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
regressor = LinearRegression()
regressor.fit(X_train, y_train)
print(regressor.intercept_)
print(regressor.coef_)
y_pred = regressor.predict(X_test)
mostrar = pd.DataFrame({'Dados reais': y_test.flatten(), 'Previsao': y_pred.flatten()})
E agora vamos comparar os dados previstos pela máquina com os dados reais, que já haviam no nosso dataset, reservados na porção de teste, primeiro em uma tabela.
mostrar
Agora vamos visualizar esses mesmos dados num gráfico, para compreendermos mais visualmente.
mostrar = mostrar.head(25)
mostrar.plot(kind='bar')
Abaixo temos um gráfico de dispersão, onde cada ponto é um volume de vendas. No eixo X temos o número de dias passados desde janeiro de 2017, e no eixo y temos o volume de vendas por dia em reais. Sobre esse gráfico, temos esta linha vermelha que representa a tendência observada pela máquina. Na prática, quer dizer que a inteligência também percebeu que conforme os dias passam, as vendas diárias tendem a aumentar.
plt.figure()
plt.scatter(X_test, y_test, color='gray')
plt.plot(X_test, y_pred, color='red', linewidth=2)
plt.show()
Agora vamos fazer nossa previsão para 01/06/2019, ou seja, 2 anos (720 dias) depois de 01/06/2017. Mas antes, vamos criar o intervalos de dias que se passarão até lá.
futuro = []
inicio = 421
for i in range(730):
inicio+=1
futuro.append(inicio)
futuro = pd.DataFrame(futuro)
futuro = np.array(futuro)
Criados os valores previstos e a lista de dias, agora vamos juntar esses valores em uma nova tabela. Nesta tabela já não utilizaremos o número de dias, mas as datas propriamente ditas.
predicoes = pd.DataFrame(regressor.predict((futuro)))
datelist = pd.date_range(start='2019-06-02', periods=730)
predicoes['data'] = datelist
predicoes.columns = 'vendas', 'data'
predicoes = predicoes[['data', 'vendas']]
predicoes
mostrar
Com o recurso da Regressão Linear poderemos escolher qualquer data (ou conjunto de datas) do intervalo de 2 anos à frente para prever as vendas do Olist, com base na linha de tendência que levantamos.
series = compras_clean[['price']]
predicoes[predicoes['data'] == '2019-08-17']
predicoes[predicoes['data'] > '2018-12-31']['vendas'].sum()
Abaixo verificamos a média de erro da predição.
RMSE = mean_squared_error(y_test, regressor.predict(X_test))**0.5
RMSE
A média de erro do nosso modelo pode flutuar R$8.644,90 reais para cima ou para baixo em cada ponto gerado, mostrando que talvez esse modelo possa ser melhorado em muito com a adição de outras métricas/variáveis, ou que talvez não seja o melhor preditor para este dataset. Mas de uma forma generalista, a tendência das vendas observada neste dataset está estabelecida.
Em todo o ano de 2019, o Olist teria vendido um total aproximado de R$ 36.085.915,00
Segundo a projeção feita sobre os dados deste dataset, no dia 17/08/2019, o Olist teria vendido aproximadamente R$ 37.373,00
Nosso objetivo aqui é encontrar entregas problemáticas e sugerir soluções para melhorar a experiência do cliente nesse ponto. É natural que este requisito vá exigir que analisemos os dataset relacionados às entregas e suas colunas, então começaremos com esta etapa.
Abaixo criaremos um dataset de nome "entregas", que terá colunas dos datasets: costumers, orders, order_items.
entregas = customers.rename(columns={'customer_zip_code_prefix':'geolocation_zip_code_prefix'})
entregas = entregas[['customer_id', 'geolocation_zip_code_prefix', 'customer_city', 'customer_state']]
entregas = entregas.merge(orders, on='customer_id', how='inner')
entregas = entregas.merge(order_items, on='order_id', how='inner')
entregas = entregas[['customer_id', 'geolocation_zip_code_prefix', 'customer_city', 'customer_state', 'order_approved_at', 'order_delivered_carrier_date', 'order_delivered_customer_date', 'order_estimated_delivery_date', 'shipping_limit_date', 'freight_value']]
Converteremos as colunas que contém informações de data para o formato datetime.
entregas['order_estimated_delivery_date'] = pd.to_datetime(entregas['order_estimated_delivery_date'])
entregas['order_delivered_customer_date'] = pd.to_datetime(entregas['order_delivered_customer_date'])
entregas['order_delivered_carrier_date'] = pd.to_datetime(entregas['order_delivered_carrier_date'])
entregas['order_approved_at'] = pd.to_datetime(entregas['order_approved_at'])
Notamos que há um bom potencial de obtenção de insights pela comparação das colunas "order_estimated_delivery_date" e "'order_delivered_customer_date", que representam o horário que estava previsto para a chegada do produto no endereço do cliente, e o horário que o produto efetivamente chegou. Então criaremos uma nova coluna chamada "dif_entregaest_x_efetiva" (diferença entre a entrega estimada e a efetiva), e a preencheremos com essa diferença.
entregas['dif_entregaest_x_efetiva'] = entregas['order_estimated_delivery_date'] - entregas['order_delivered_customer_date']
Vamos eliminar todas as linhas que não tenham registros de datas, para uma análise mais precisa.
entregas = entregas.dropna()
Agora uma rápida olhada nas entregas cujo prazo efetivo foi maior do que o prazo previsto.
entregas[entregas['dif_entregaest_x_efetiva'] < '0 days']
Temos uma hipótese. Talvez esses atrasos se devam a algum processo na entrega do produto à transportadora? A coluna "order_delivered_carrier_date" pode nos ajudar a verificar isso.
Vamos criar uma nova coluna chamada "horario_despachado", com dados de "order_delivered_carrier_date", mas num formato que possa ser trabalhado com operações matemáticas.
entregas['horario_despachado'] = entregas['order_delivered_carrier_date'].dt.hour
entregas['dif_entregaest_x_efetiva'] = entregas['dif_entregaest_x_efetiva'].astype(str)
entregas['dif_entregaest_x_efetiva'] = entregas['dif_entregaest_x_efetiva'].str.replace(' days.*', '')
entregas['dif_entregaest_x_efetiva'] = entregas['dif_entregaest_x_efetiva'].astype(int)
Agora vamos verificar as entregas à transportadora/correios que ocorreram após as 18h.
entregas[entregas['horario_despachado'] > 18]
Vamos conhecer agora quais os horários de entrega à transportadora/correios que mais provocaram atrasos na entrega ao cliente final.
Na coluna da esquerda vemos a hora, e na coluna da direita vemos a quantidade de entregas atrasadas.
entregas[entregas['dif_entregaest_x_efetiva'] < 0]['horario_despachado'].value_counts().sort_index()
Parece que há um padrão aqui. Mercadorias despachadas entre meio dia e meia noite estão entre as que mais atrasam.
Vamos ver as mercadorias que chegaram na data prevista ou antes, para verificar quais os horário mais frequentes em que elas foram entregues à transportadora/correios.
Na coluna da esquerda vemos a hora, e na coluna da direita vemos a quantidade de entregas adiantadas.
entregas[entregas['dif_entregaest_x_efetiva'] >= 0]['horario_despachado'].value_counts().sort_index()
Sabendo que neste dataset há muito mais entregas adiantadas (ou no prazo) do que atrasadas, a comparação direta não nos ajudou. Vamos calcular a proporção de atraso entre os horários e plotá-la. Para isso vai ser necessário criar um pequeno laço de repetição, feito abaixo.
percent_atraso_hora_despachado = {}
for i in range(24):
hora = i+1
if hora < 24:
percentual_atraso = len(entregas[(entregas['horario_despachado'] == hora) & (entregas['dif_entregaest_x_efetiva'] < 0)])/len(entregas[(entregas['horario_despachado'] == hora)])
percent_atraso_hora_despachado[hora] = percentual_atraso
else:
percentual_atraso = len(entregas[(entregas['horario_despachado'] == 0) & (entregas['dif_entregaest_x_efetiva'] < 0)])/len(entregas[(entregas['horario_despachado'] == 0)])
percent_atraso_hora_despachado[hora] = percentual_atraso
Agora vamos criar uma tabela no formato DataFrame para vermos os horários e seu percentual de atraso, e plotá-la.
percent_atraso_hora_despachado = pd.DataFrame.from_dict(percent_atraso_hora_despachado, orient='index')
percent_atraso_hora_despachado['hora'] = percent_atraso_hora_despachado.index
percent_atraso_hora_despachado.columns = 'percentual de atraso', 'hora de despacho'
percent_atraso_hora_despachado = percent_atraso_hora_despachado[['hora de despacho', 'percentual de atraso']]
percent_atraso_hora_despachado['percentual de atraso'] = percent_atraso_hora_despachado['percentual de atraso']*100
percent_atraso_hora_despachado.plot(x='hora de despacho', y='percentual de atraso', legend=False)
plt.xticks(percent_atraso_hora_despachado['hora de despacho'])
plt.ylabel('percentual de atraso')
Aparentemente, os horários de despacho que com as menores proporção de atrasos na entrega final para o cliente são 9h, 10h e 11h da manhã. Vamos verificar em quais horários os vendedores do Olist mais estão despachando produtos.
entregas['horario_despachado'].value_counts().plot(kind='bar', rot=0)
plt.ylabel('Número de produtos despachados')
plt.xlabel('Hora despachada')
plt.title('Produtos despachados à transportadora x horário de despacho')
Os vendedores do Olist têm despachado a maior parte dos produtos às 14h, 15h e 19h, horários que não são os de melhores performances para entrega sem atraso. É provável que haja uma redução dos atrasos que existem hoje se os vendedores forem orientados a fazer entregas à transportadora/correios nos horários de 9h, 10h e 11h. Mas é preciso testar essa hipótese.
Temos como hipótese que 9h, 10h e 11h serão horários melhores do que 14h, 15h e 19h para despachar produtos. Vamos usar uma estatística de teste para essa hipótese.
grupo_a = entregas[(entregas['dif_entregaest_x_efetiva'] < 0) & ((entregas['horario_despachado'] == 14) | (entregas['horario_despachado'] == 15) | (entregas['horario_despachado'] == 19))]
grupo_b = entregas[(entregas['dif_entregaest_x_efetiva'] < 0) & ((entregas['horario_despachado'] == 9) | (entregas['horario_despachado'] == 10) | (entregas['horario_despachado'] == 11))]
xa = grupo_a['dif_entregaest_x_efetiva'].mean()
xb = grupo_b['dif_entregaest_x_efetiva'].mean()
estatistica_teste = xb-xa
estatistica_teste
A hipótese já estaria anulada se a diferença entre as médias do grupo A (entregas às 14) e do grupo B (entregas às 10) fosse igual a zero. Há realmente uma diferença matemática entre os dois grupos, mas agora vamos verificar se isso não se deve ao acaso, calculando Estatística de Teste e o Nível de Significância, representado no p-value, que deverá resultar num número abaixo de 0.5 para confirmar nossa hipótese.
Vamos precisar criar um grande grupo com uma fatia do dataset, incluindo os valores de diferença da entrega estimada x efetiva dos produtos despachados 14h e dos despachados às 10.
dois_grupos = pd.concat([grupo_a, grupo_b], ignore_index=True)
dois_grupos['horario_despachado'].unique()
todosgrupos = dois_grupos['dif_entregaest_x_efetiva'].to_list()
media_dif = []
for i in range(1000):
grupo_a = []
grupo_b = []
for valor in todosgrupos:
grupo = np.random.rand()
if grupo >= 0.5:
grupo_a.append(valor)
else:
grupo_b.append(valor)
media_a = np.mean(grupo_a)
media_b = np.mean(grupo_b)
diferenca_media = media_b = media_a
media_dif.append(diferenca_media)
sampling_dist = {}
for value in media_dif:
if sampling_dist.get(value, False):
newval = sampling_dist.get(value)
newval += 1
else:
sampling_dist[value] = 1
freqs = []
for key in sampling_dist:
if key >= estatistica_teste:
freqs.append(key)
p_value = sum(freqs)
p_value /= 1000
p_value
Com p_value igual a 0.0, podemos dizer que é estatisticamente relevante a diferença de resultado nos atrasos de entregas entre os grupos A e B, não sendo essas diferenças dadas ao acaso. Ou seja, a hipótese tem força estatística.
Há uma boa oportunidade para reduzir a quantidade de entregas atrasadas, simplesmente trocando os horários mais frequentes de despacho de mercadorias à transportadora. No intervalo em que esses dados foram gerados os vendedores do Olist utilizavam mais os horários de 14h, 15h e 19h para enviar a maior parte das mercadorias, mas poderiam ter tido menos atrasos se utilizassem os horários de 9h, 10h e 11h.
Agora removeremos as variáveis criadas para este requisito, visando aliviar a memória usada.
del percent_atraso_hora_despachado
del entregas
del todosgrupos
del dois_grupos
del grupo_a
del grupo_b
del media_dif
del sampling_dist
Neste requisito faremos uma análise cruzando os dados relacionados a produtos com a localização dos clientes. O intuito será descobrir padrões de comportamento nos clientes e segmentá-los pelas regiões do Brasil: Sudeste, Sul, Nordeste, Norte e Centro-Oeste.
Abaixo, carregamos o dataset de clientes e fazemos as fusões necessárias para que ele tenha informações de produtos e vendedores.
customers = customers[['customer_id', 'customer_state']]
produtos = products.merge(order_items, on='product_id')
produtos = produtos.drop(columns=['product_weight_g', 'product_length_cm', 'product_height_cm', 'product_width_cm', 'order_item_id', 'shipping_limit_date'])
produtos = produtos.merge(sellers, on='seller_id', how='inner')
customers = customers.merge(orders, on='customer_id', how='inner')
customers = customers.merge(produtos, on='order_id', how='inner')
customers
Para facilitar o nosso trabalho, criaremos uma nova coluna, de nome "regiao", e as populamos com as condicionais para os estados.
sul = customers[(customers['customer_state'] == 'PR') | (customers['customer_state'] == 'SC') | (customers['customer_state'] == 'RS')]
sudeste = customers[(customers['customer_state'] == 'SP') | (customers['customer_state'] == 'MG') | (customers['customer_state'] == 'RJ') | (customers['customer_state'] == 'ES')]
nordeste = customers[(customers['customer_state'] == 'AL') | (customers['customer_state'] == 'BA') | (customers['customer_state'] == 'CE') | (customers['customer_state'] == 'MA') | (customers['customer_state'] == 'PB') | (customers['customer_state'] == 'PE') | (customers['customer_state'] == 'PI') | (customers['customer_state'] == 'RN') | (customers['customer_state'] == 'SE')]
centro_oeste = customers[(customers['customer_state'] == 'GO') | (customers['customer_state'] == 'MT') | (customers['customer_state'] == 'MS')]
norte = customers[(customers['customer_state'] == 'AC') | (customers['customer_state'] == 'AP') | (customers['customer_state'] == 'AM') | (customers['customer_state'] == 'PA') | (customers['customer_state'] == 'RO') | (customers['customer_state'] == 'RR') | (customers['customer_state'] == 'TO')]
customers.loc[(customers['customer_state'] == 'PR') | (customers['customer_state'] == 'SC') | (customers['customer_state'] == 'RS'), 'regiao'] = 'sul'
customers.loc[(customers['customer_state'] == 'SP') | (customers['customer_state'] == 'MG') | (customers['customer_state'] == 'RJ') | (customers['customer_state'] == 'ES'), 'regiao'] = 'sudeste'
customers.loc[(customers['customer_state'] == 'AL') | (customers['customer_state'] == 'BA') | (customers['customer_state'] == 'CE') | (customers['customer_state'] == 'MA') | (customers['customer_state'] == 'PB') | (customers['customer_state'] == 'PE') | (customers['customer_state'] == 'PI') | (customers['customer_state'] == 'RN') | (customers['customer_state'] == 'SE'), 'regiao'] = 'nordeste'
customers.loc[(customers['customer_state'] == 'GO') | (customers['customer_state'] == 'MT') | (customers['customer_state'] == 'MS'), 'regiao'] = 'centro-oeste'
customers.loc[(customers['customer_state'] == 'AC') | (customers['customer_state'] == 'AP') | (customers['customer_state'] == 'AM') | (customers['customer_state'] == 'PA') | (customers['customer_state'] == 'RO') | (customers['customer_state'] == 'RR') | (customers['customer_state'] == 'TO'), 'regiao'] = 'norte'
Criada a nova coluna, agora poderemos avaliar que tipos de produtos as regiões mais consomem.
customers[customers['regiao'] == 'sul']['product_category_name'].value_counts()
customers[customers['regiao'] == 'sudeste']['product_category_name'].value_counts()
customers[customers['regiao'] == 'nordeste']['product_category_name'].value_counts()
customers[customers['regiao'] == 'centro-oeste']['product_category_name'].value_counts()
customers[customers['regiao'] == 'norte']['product_category_name'].value_counts()
Abaixo temos um ranking, com as 4 de categorias de produtos mais consumidas em cada região do Brasil.
| Sul | Sudeste | Nordeste | Norte | Centro Oeste | |
|---|---|---|---|---|---|
| 1º | Móveis e decoracao | Cama, mesa e banho | Beleza e saúde | Beleza e saúde | Beleza e saúde |
| 2º | Cama, mesa e banho | Beleza e saúde | Relógios e presentes | Informática e acessórios | Cama, mesa e banho |
| 3º | Esporte e lazer | Esporte e lazer | Esporte e lazer | Esporte e lazer | Esporte e lazer |
| 4º | Informática e acessórios | Móveis e Decoração | Informática e acessórios | Telefonia | Móveis e Decoração |
Verificaremos os gastos totais por região logo abaixo e plotaremos num gráfico de barras.
customers.groupby(by='regiao').agg('sum').astype(int)[['price']].plot(kind='bar', legend=False)
plt.ylabel('Valor absoluto gasto em milhões')
plt.xlabel('')
Conforme já era de se esperar, a região que mais compra em números absolutos é a Sudeste, seguida da Sul. Mas isso apenas não é tão interessante.
Vamos verificar agora o gasto médio por região em um gráfico.
customers.groupby(by='regiao').agg('mean').astype(int)[['price']].plot(kind='bar', legend=False, rot=45)
plt.xlabel('')
plt.ylabel('Gasto médio por compra em Reais')
De forma bastante interessante, observamos que os públicos das regiões Norte e Nordeste são os que mais gastam na média.
Vamos utilizar um boxplot para comparar lado a lado os gastos das regiões, conhecendo a distribuição desses valores de uma maneira mais visual
b = sns.boxplot(showfliers=False, data = customers[['price', 'regiao']],
x = 'regiao',
y = 'price',
order = ['sudeste', # custom order of boxplots
'sul',
'nordeste',
'norte',
'centro-oeste'])
plt.ylabel('Valor médio gasto por compra')
plt.xlabel('')
Com o boxplot é possível ter uma ideial mais visual de como estão os gastos e suas distribuições. Em comparação com outras regiões, Norte e Nordeste têm gasto mínimo maior do que as outras, também têm média de gasto maior, e o volume de gastos destas duas está concentrado numa área de maior valor.
Temos uma hipótese para esta observação. Talvez os moradores das regiões norte e nordeste gastem mais por compra porque precisam aproveitar melhor o frete, já que ele pode ser mais caro? Vamos ver quanto gasta cada região em frete para entender melhor.
b = sns.boxplot(showfliers=False, data = customers,
x = 'regiao',
y = 'freight_value',
order = ['sudeste', # custom order of boxplots
'sul',
'nordeste',
'norte',
'centro-oeste'])
plt.ylabel('Valor médio gasto por frete')
plt.xlabel('')
De fato, as regiões Norte e Nordeste têm gasto consideralmente mais em frete do que outras regiões.
Neste requisito pudemos analisar as categorias de produtos que as regiões mais consomem, e também observamos que moradores das regiões norte e nordeste, apesar de na soma geral gastarem menos que Sul e Sudeste, tendem a gastar mais por compra e mais no frete. Fica um ponto de atenção: este público está disposto a gastar mais, e talvez isso possa ser uma oportunidade para o Olist.
del b
del customers
Para este requisito vamos cruzar dados de produtos, de reviews e de clientes, visando obter informações sobre a satisfação desses.
Iniciamos o trabalho neste requisito fundindo os datasets "customers", "order_reviews" e "products".
order_reviews = order_reviews.merge(orders, on='order_id', how='inner')
order_reviews.info()
order_reviews = order_reviews.merge(order_items, on='order_id', how='inner')
order_reviews = order_reviews.merge(products, on='product_id', how='inner')
order_reviews
Vamos visualizar o nosso novo dataset, suas colunas, número de linhas etc. Logo abaixo removeremos pedidos duplicados, geradas como resultado das fusões.
order_reviews.info()
order_reviews.drop_duplicates(subset ="order_id", keep = False, inplace = True)
Agora que os três datasets foram fundidos, vamos conhecer as principais reclamações dos clientes insatisfeitos, por meio de nuvens de palavras com o título do review e a mensagem. Vamos considerar um cliente insatisfeito aquele que deixou uma nota 1 ou 2, e assim vamos fatiar o nosso dataset.
insatisfeitos = order_reviews[(order_reviews['review_score'] == 1) | (order_reviews['review_score'] == 2)]
titulos = insatisfeitos['review_comment_title'].str.cat(sep=' ')
wordcloud = WordCloud(background_color="white", scale=2).generate(titulos)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
mensagens = insatisfeitos['review_comment_message'].str.cat(sep=' ')
wordcloud = WordCloud(background_color="white", scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
Vimos que em sua maioria, os clientes insatisfeitos relataram problemas com a entrega, muitos afirmando que não tinham recebido o produto comprado até então.
len(insatisfeitos)
Temos 11541 entradas no dataset de insatisfeitos. Vamos conhecer melhor as categorias que mais receberam notas ruins.
insatisfeitos['product_category_name'].value_counts()
É interessante perceber que as categorias que mais receberam reclamações também estão entre as mais vendidas, conforme vimos na análise de outros requisitos. Vamos verificar as categorias que receberam menos reclamações, no caso, notas maiores que 2.
order_reviews[order_reviews['review_score'] > 2 ]['product_category_name'].value_counts()
São basicamente as mesmas categorias que recebem as piores e as melhores notas. Talvez o problema não seja com a categoria em si, mas com o fornecedor. Vamos verificar então a proporção de insatisfeitos para a que tem provocado maior insatisfação: cama, mesa e banho
len(order_reviews[(order_reviews['product_category_name'] == 'cama_mesa_banho') & (order_reviews['review_score'] <= 2)]) / len(order_reviews[(order_reviews['product_category_name'] == 'cama_mesa_banho') & (order_reviews['review_score'] > 2)])
Mais de 17% das pessoas que compram cama, mesa e banho ficaram insatisfeitas com o seu pedido. É um ponto de atenção. Vamos verificar a categoria de Beleza e Saúde.
len(order_reviews[(order_reviews['product_category_name'] == 'beleza_saude') & (order_reviews['review_score'] <= 2)]) / len(order_reviews[(order_reviews['product_category_name'] == 'beleza_saude') & (order_reviews['review_score'] > 2)])
A insatisfação para a segunda categoria também é alta, quase 14%. Vamos conhecer os vendedores que mais geraram reclamações.
insatisfeitos['seller_id'].value_counts()
Os vendedores de id "4a3ca9315b744ce9f8e9374361493884", "6560211a19b47992c3666cc44a7e94c0" e "cc419e0650a3c5ba77189a1882b7556a" são os que mais receberam reclamações. Conheceremos agora as nuvens de palavras para os reviews que eles receberam.
mensagens = insatisfeitos[insatisfeitos['seller_id'] == '4a3ca9315b744ce9f8e9374361493884']['review_comment_message'].str.cat(sep=' ').replace('de', '').replace('que', '').replace('um', '').replace('produto', '').replace('muito', '').replace('da', '').replace('na', '').replace('para', '').replace('ma', '').replace('meu', '')
wordcloud = WordCloud(background_color="white", scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
E vendedor de id "4a3ca9315b744ce9f8e9374361493884" está relacionado com produtos de cama mesa e banho (vemos na nuvem palavras como "tapete", "toalha", "costura", "tecido", "lençol", "colcha" e o que parece ser "cortina", mas cortado) e as reclamações estão focadas principalmente na não entrega dos produtos, mas também é considerável que tenham entregue produtos com cores e outras características diferentes das anunciadas. O grande "não" é resultado de frases frequentes de valência negativa.
mensagens = insatisfeitos[insatisfeitos['seller_id'] == '6560211a19b47992c3666cc44a7e94c0']['review_comment_message'].str.cat(sep=' ').replace('de', '').replace('que', '').replace('um', '').replace('produto', '').replace('muito', '').replace('da', '').replace('na', '').replace('para', '').replace('ma', '').replace('meu', '').replace('Produto', '').replace('veio', '').replace('veio', '')
wordcloud = WordCloud(background_color="white", scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
O vendedor de id "6560211a19b47992c3666cc44a7e94c0" está relacionado com relojoaria, tem tido problemas com a entrega, e seus produtos têm apresentado diversos defeitos/problemas, como podemos perceber em "troca", "soltando", "ruim", "Péssimo", "baixa quali", "funciondo", "fraco". Obviamente, o grande "não" é resultado de frases frequentes de valência negativa.
mensagens = insatisfeitos[insatisfeitos['seller_id'] == 'cc419e0650a3c5ba77189a1882b7556a']['review_comment_message'].str.cat(sep=' ').replace('de', '').replace('que', '').replace('um', '').replace('produto', '').replace('muito', '').replace('da', '').replace('na', '').replace('para', '').replace('ma', '').replace('meu', '').replace('Produto', '').replace('veio', '').replace('veio', '')
wordcloud = WordCloud(background_color="white", scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
O vendedor de id "cc419e0650a3c5ba77189a1882b7556a" provavelmente está relacionado ao ramo de Saúde e Beleza, pois nos seus comentários aparecem as palavras "tesoura", "pincéis", "escova" e "cabelo". Também tem tido dificuldades com os prazos de entrega e possivelmente também no atendimento ao cliente (nas frases "Quero resposta", "esperando" e na palavra "retorno").
mensagens = insatisfeitos[insatisfeitos['product_category_name'] == 'informatica_acessorios']['review_comment_message'].str.cat(sep=' ').replace('de', '').replace('que', '').replace('um', '').replace('produto', '').replace('muito', '').replace('da', '').replace('na', '').replace('para', '').replace('ma', '').replace('meu', '').replace('Produto', '').replace('veio', '').replace('veio', '')
wordcloud = WordCloud(background_color="white", scale=2).generate(mensagens)
plt.figure(figsize=(18, 10), dpi=100)
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
Analisando agora o segmento de Informática e Acessórios como um todo, as reclamações são muito parecidas com as de outros segmentos, sugerindo um problema com a logística.
Mas é realmente engraçado que nesses comentários tenham aparecido nomes das famílias da série Game of Thrones. Vamos dar uma olhada nisso.
insatisfeitos = insatisfeitos.dropna()
pd.set_option('display.max_colwidth', -1)
insatisfeitos[insatisfeitos['review_comment_message'].str.contains('targaryen')]['review_comment_message']
insatisfeitos[insatisfeitos['review_comment_message'].str.contains('lannister')]['review_comment_message']
insatisfeitos[insatisfeitos['review_comment_message'].str.contains('stark')]['review_comment_message']
É, parece que a situação tá complicada para os membros das casas de Westeros.
Analisando nosso dataset pudemos ver que as categorias de produtos que têm tido mais reclamações são "cama, mesa e banho", "beleza e saúde" e "informatica_acessorios". As reclamações estão relacionadas às entregas e à qualidade dos produtos em si. E as famílias de Westeros estão tendo uma grande dificuldade com logística.
del insatisfeitos
del order_reviews
del mensagens
pd.set_option('display.max_colwidth', 50)
Para este requisito vamos revisitar os três modelos de precificação mais comuns decidir como seria melhor usá-los, claro, justificando a escolha.
Os modelos de precificação mais comuns na realidade brasileira são:
O modelo baseado na concorrência. Tem como maior peso na precificação o benchmarking com os preços do(s) concorrente(s). Para ser funcional, este modelo requer monitoramento permanente dos preços e das ações praticados no mercado.
O modelo baseado no consumidor. Se apoia na percepção de valor que o cliente tem de certo produto. Para funcionar, é preciso manter uma iniciativa/ação fixa de captação da opinião dos compradores sobre um ou mais produtos.
O modelo baseado em custos. É a abordagem mais conservadora das três. A precificação é mais estável, feita em cima do custo de aquisição por unidade do produto, somado ao lucro desejado, expresso na fórmula: custo de aquisição + ((percentual de lucro desejado / 100) x custo de aquisição)
No grande varejo há uma tendência a assumir o modelo baseado na concorrência, e essa poderia ser a indicação mais óbvia para o Olist. Mas nós dispomos de ferramentas na Data Science que nos possibilitam praticar um modelo híbrido, ou pelo menos com algumas vantagens de outros, melhorando sua performance.
Como seria um modelo de precificação "quase híbrido" para os produtos do Olist
O modelo que se sugere toma como ponto de partida a lógica tradicional do varejo (ou seja, sugere-se o modelo baseado na concorrência) , sempre utilizando o benchmarking dos valores praticados pela concorrência como norte. Mas este seria apenas o ponto de partida. Abaixo estão descritas algumas ações adicionais (cuja orientação varia entre um modelo e outro) capazes de ajudar a enriquecer a precificação do Olist:
Sugerimos o modelo baseado na concorrência como o ideal para precificação no Olist, mas "engrossado" com recursos do Data Science que na teoria são pertencentes a outros modelos.
